package gm.commands;

import com.martiansoftware.jsap.FlaggedOption;
import com.martiansoftware.jsap.JSAP;
import com.martiansoftware.jsap.JSAPException;
import com.martiansoftware.jsap.JSAPResult;
import com.martiansoftware.jsap.Switch;
import com.martiansoftware.jsap.UnflaggedOption;
import gm.GM;
import gm.GmLog;
import gm.server.Machine;
import gm.server.Motor;
import gm.server.Server;
import gm.server.Tip;
import gm.server.WorkingArea;
import gm.server.WrongPositionException;
import gm.server.database.HoleRawDb;
import gm.server.geom.Vector2;
import java.io.File;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

/**
 * Hardverrel kapcsolatos parancsok gyűjteménye.
 *
 * @author imruf84
 */
public abstract class HardwareCommands extends CommandSet implements HardwareCommandsInterface {

    /**
     * Konstruktor.
     *
     * @param server szerver
     */
    public HardwareCommands(final Server server) {
        super(server);
        createCommands();
    }

    @Override
    protected final void createCommands() {
        try {
            getInfo();
            motorControl();
            workingArea();
            scanHoles();
        } catch (JSAPException ex) {
            GmLog.printError(ex);
        }
    }

    /**
     * Rendszerinformációk lekérdezése.
     */
    private void getInfo() {
        Command c = new Command("getinfo", new RunnableCommand() {
            @Override
            public String execute(final Command command, final String sCommand, final JSAPResult result, final PrintWriter out) {
                return getInfo(command, sCommand, result, out);
            }
        });

        c.setHelp("Get basic informations about the hardware.");

        put(c.getName(), c);
    }

    @Override
    public String getInfo(final Command command, final String sCommand, final JSAPResult result, final PrintWriter out) {
        return getServer().toString();
    }

    /**
     * Motor vezérlése.
     *
     * @throws JSAPException kivétel
     */
    private void motorControl() throws JSAPException {
        final String motorID = "motorIDs";
        final String rotate = "rotate";

        Command c = new Command("motor", new RunnableCommand() {
            @Override
            public String execute(final Command command, final String sCommand, final JSAPResult result, final PrintWriter out) {

                String sResult = "";

                // Ha forgatás van adott értékkel.
                if (result.contains(rotate)) {
                    final int vRotate = result.getInt(rotate);
                    String[] vMotorID = result.getStringArray(motorID);

                    ArrayList<Thread> ta = new ArrayList<>();

                    for (String mID : vMotorID) {
                        final Motor m = getServer().getMachine().getMotor(mID);
                        if (null != m) {
                            // Szál létrehozása majd tárolása.
                            Thread t = new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    m.doRotations(vRotate);
                                }
                            });
                            ta.add(t);
                        } else {
                            sResult += "Unknown motor ID:" + mID + "\n";
                        }
                    }

                    // Motorok vezérlésének indítása.
                    for (Thread t : ta) {
                        t.start();
                    }

                    // Minden motor egyszerre fog végezni.
                    for (Thread t : ta) {
                        try {
                            t.join();
                        } catch (InterruptedException ex) {
                        }
                    }

                    return sResult;
                }

                return "";
            }
        });

        UnflaggedOption pMotorID = new UnflaggedOption(motorID)
                .setStringParser(JSAP.STRING_PARSER)
                .setRequired(true)
                .setGreedy(true);
        c.setHelp("Motors ID list.");
        c.registerParameter(pMotorID);

        FlaggedOption pRotate = new FlaggedOption(rotate)
                .setStringParser(JSAP.INTEGER_PARSER)
                .setShortFlag('r')
                .setLongFlag(rotate)
                .setRequired(false);
        c.setHelp("Rotates the motor by the value.");
        c.registerParameter(pRotate);

        c.setHelp("Control motors.");

        put(c.getName(), c);
    }

    /**
     * Munkaterület sarokpontjainak a tárolása.
     *
     * @throws JSAPException kivétel
     */
    private void workingArea() throws JSAPException {
        final String cornerIDs = "cornerID";
        final String store = "store";

        Command c = new Command("workarea", new RunnableCommand() {
            @Override
            public String execute(final Command command, final String sCommand, final JSAPResult result, final PrintWriter out) {

                String sResult = "";

                // Ha forgatás van adott értékkel.
                if (result.contains(store)) {
                    int[] vCornerID = result.getIntArray(cornerIDs);

                    for (int cID : vCornerID) {
                        switch (cID) {
                            case 1:
                                getServer().getMachine().getWorkingArea().storeCorner1ToCurrent();
                                break;
                            case 2:
                                getServer().getMachine().getWorkingArea().storeCorner2ToCurrent();
                                break;
                            default:
                                sResult += "Unknown corner index:" + cID + "\n";
                        }
                    }

                    return sResult;
                }

                return "";
            }
        });

        UnflaggedOption pCornerID = new UnflaggedOption(cornerIDs)
                .setStringParser(JSAP.INTEGER_PARSER)
                .setRequired(true)
                .setGreedy(true);
        c.setHelp("Corners index list (1,2).");
        c.registerParameter(pCornerID);

        Switch pStore = new Switch(store)
                .setShortFlag('s')
                .setLongFlag(store);

        c.setHelp("Store the motors absolute steps.");
        c.registerParameter(pStore);

        c.setHelp("Handle working area.");

        put(c.getName(), c);
    }

    /**
     * Kanava letapogatása.
     *
     * @throws JSAPException kivétel
     */
    private void scanHoles() throws JSAPException {

        final String discover = "discover";
        final String group = "group";

        Command c = new Command("scan", new RunnableCommand() {
            @Override
            public String execute(final Command command, final String sCommand, final JSAPResult result, final PrintWriter out) {

                /*String dbPath = "./database/";
                 String holesRawDbName = "holesraw.odb";
                 new File(dbPath + holesRawDbName).delete();
                 EntityManagerFactory hrdb = Persistence.createEntityManagerFactory(dbPath + holesRawDbName);
                 EntityManager hr = hrdb.createEntityManager();
                
                 // Teljes tartalom törlése.
                 hr.getTransaction().begin();
                 for (HoleRawDb h : hr.createQuery("SELECT hr FROM HoleRawDb hr", HoleRawDb.class).getResultList()) {
                 hr.remove(h);
                 }
                 hr.getTransaction().commit();
                
                 // Új adatok beszúrása.
                 hr.getTransaction().begin();
                 hr.persist(new HoleRawDb(10, 20));
                 hr.getTransaction().commit();
                
                 // Adatok kiírása.
                 for (HoleRawDb h : hr.createQuery("SELECT hr FROM HoleRawDb hr", HoleRawDb.class).getResultList()) {
                 System.out.println(h.toString());
                 }
                
                 hr.close();
                 hrdb.close();
                
                 return "";*/
                out.println("Connecting to database...");

                String dbPath = "./database/";
                String holesRawDbName = "holesraw.odb";

                if (result.contains(discover)) {
                    
                    long startTime = System.currentTimeMillis();
                    WrongPositionException error = null;

                    // Töröljük a táblákat és létrehozzuk a szükségeset.
                    new File(dbPath + holesRawDbName).delete();
                    EntityManagerFactory hrdb = Persistence.createEntityManagerFactory(dbPath + holesRawDbName);
                    EntityManager hr = hrdb.createEntityManager();

                    // Munkaterület bal felső sarkába mozgatjuk a végpontot.
                    out.println("Moving tip to the top left corner...");

                    Machine m = getServer().getMachine();
                    WorkingArea wa = m.getWorkingArea();

                    double tlSteps[];
                    try {
                        tlSteps = wa.getTopLeftCornerInStepsAbsolute(m);
                        m.doMotorStepsTo(tlSteps[0], tlSteps[1]);

                        // Kezdődhet a munka.
                        double fromY = Math.min(wa.getCorner1(true).getY(), wa.getCorner2(true).getY());
                        double toY = Math.max(wa.getCorner1(true).getY(), wa.getCorner2(true).getY());
                        double toX = Math.min(wa.getCorner1(true).getX(), wa.getCorner2(true).getX());
                        Tip tip = m.getTip();
                        double dSteps = (double) result.getInt(discover);

                        out.println("Scanning kanava...");

                        Vector2 local, global;
                        while (tip.calculatePositionLocal().x > toX) {
                            while (tip.calculatePositionLocal().y < toY) {
                                m.doMotorSteps(dSteps, dSteps);
                                local = tip.calculatePositionLocal();
                                global = tip.calculatePositionGlobal();
                                hr.getTransaction().begin();
                                hr.persist(new HoleRawDb(
                                        m.getMotorLeft().getCurrentStepsAbsolute(),
                                        m.getMotorRight().getCurrentStepsAbsolute(),
                                        local.x, local.y,
                                        global.x, global.y,
                                        m.testHole()
                                ));
                                hr.getTransaction().commit();
                            }
                            m.doMotorSteps(dSteps, 0);
                            while (tip.calculatePositionLocal().y > fromY) {
                                m.doMotorSteps(-dSteps, -dSteps);
                                local = tip.calculatePositionLocal();
                                global = tip.calculatePositionGlobal();
                                hr.getTransaction().begin();
                                hr.persist(new HoleRawDb(
                                        m.getMotorLeft().getCurrentStepsAbsolute(),
                                        m.getMotorRight().getCurrentStepsAbsolute(),
                                        local.x, local.y,
                                        global.x, global.y,
                                        m.testHole()
                                ));
                                hr.getTransaction().commit();
                            }
                            m.doMotorSteps(0, -dSteps);
                        }

                    } catch (WrongPositionException ex) {
                        error = ex;
                    }
                    
                    // Adatbáziskapcsolat bontása.
                    hr.close();
                    hrdb.close();
                    out.println("Time:" + GM.GetFormattedInterval(System.currentTimeMillis() - startTime));
                    
                    // Hiba esetén küldünk mindent és kilépünk.
                    if (null != error) {
                        GmLog.printError(error);
                        return error.getLocalizedMessage();
                    }

                } // discover
/*
                // Pontok csoportosítása.
                if (result.getBoolean(group)) {
                    long startTime = System.currentTimeMillis();

                    out.println("Calculating points distanses...");
                    stat.execute(
                            "DROP TABLE IF EXISTS holes_distances;"
                            + "CREATE TABLE IF NOT EXISTS holes_distances "
                            + "("
                            + "ID1 INTEGER," // első pont azonosítója
                            + "ID2 INTEGER," // mádosik pont azonosítója
                            + "locX1 DOUBLE DEFAULT 0," // első pont lokális x koordinátája
                            + "locY1 DOUBLE DEFAULT 0," // első pont lokális y koordinátája
                            + "globX1 DOUBLE DEFAULT 0," // első pont globális x koordinátája
                            + "globY1 DOUBLE DEFAULT 0," // első pont globális y koordinátája
                            + "locX2 DOUBLE DEFAULT 0," // második pont lokális x koordinátája
                            + "locY2 DOUBLE DEFAULT 0," // második pont lokális y koordinátája
                            + "globX2 DOUBLE DEFAULT 0," // második pont globális x koordinátája
                            + "globY2 DOUBLE DEFAULT 0," // második pont globális y koordinátája
                            + "d DOUBLE DEFAULT 0," // távolság
                            + ")"
                    );
                    stat.execute(
                            "INSERT INTO holes_distances (ID1,ID2,d,locX1,locY1,globX1,globY1,locX2,locY2,globX2,globY2) "
                            + "SELECT h1.ID AS ID1,h2.ID AS ID2,POWER(h1.locX-h2.locX,2)+POWER(h1.locY-h2.locY,2) AS d,h1.locX AS locX1,h1.locY AS locY1,h1.globX AS globX1,h1.globY AS globY1,h2.locX AS locX2,h2.locY AS locY2,h2.globX AS globX2,h2.globY AS globY2 "
                            + "FROM holes_raw h1,holes_raw h2 "
                            + "WHERE h1.isHole AND h2.isHole AND h2.ID < h1.ID;"
                            + "DELETE FROM holes_distances WHERE d > 0.8;"
                            + "INSERT INTO holes_distances (ID1,ID2,d,locX1,locY1,globX1,globY1,locX2,locY2,globX2,globY2) SELECT ID2 AS ID1,ID1 AS ID2,d,locX2,locY2,globX2,globY2,locX1,locY1,globX1,globY1 FROM holes_distances;"
                            + "INSERT INTO holes_distances (ID1,ID2,d,locX1,locY1,globX1,globY1,locX2,locY2,globX2,globY2) SELECT ID AS ID1,ID AS ID2,0 AS d,locX AS locX1,locY AS locY1,globX AS globX1,globY AS globY1,locX AS locX2,locY AS locY2,globX AS globX2,globY AS globY2 FROM holes_raw WHERE isHole;"
                    );

                    out.println("Grouping points...");
                    stat.execute(
                            "DROP TABLE IF EXISTS holes_grouped;"
                            + "CREATE TABLE IF NOT EXISTS holes_grouped "
                            + "("
                            + "ID INTEGER AUTO_INCREMENT, PRIMARY KEY(ID)," // azonosító
                            + "locX DOUBLE DEFAULT 0," // lokális x koordináta (ide tartozó pontok átlaga)
                            + "locY DOUBLE DEFAULT 0," // lokális y koordináta (ide tartozó pontok átlaga)
                            + "globX DOUBLE DEFAULT 0," // globális x koordináta (ide tartozó pontok átlaga)
                            + "globY DOUBLE DEFAULT 0," // globális y koordináta (ide tartozó pontok átlaga)
                            + ")"
                    );
                    stat.execute(
                            "INSERT INTO holes_grouped (locX,locY,globX,globY) "
                            + "SELECT DISTINCT AVG(locX1) AS locX,AVG(locY1) AS locY,AVG(globX1) AS globX,AVG(globY1) AS globY "
                            + "FROM holes_distances "
                            + "GROUP BY ID2");

                    /*stat.execute(
                     "INSERT INTO holes_grouped (locX,locY,globX,globY) SELECT DISTINCT AVG(h2.locX) AS locX,AVG(h2.locY) AS locY,AVG(h2.globX) AS globX,AVG(h2.globY) AS globY "
                     + "FROM holes_raw h1,holes_raw h2 "
                     + "WHERE h1.isHole AND h2.isHole AND POWER(h1.locX-h2.locX,2)+POWER(h1.locY-h2.locY,2) < 0.8 "
                     + "GROUP BY h1.ID ");*/
/*                    out.println("Time:" + GM.GetFormattedInterval(System.currentTimeMillis() - startTime));

                } // group

            }

        } catch (ClassNotFoundException | SQLException ex) {
                    GmLog.printError(ex);
                    return ex.getLocalizedMessage();
                }
*/
        return "";
    }
}
);

        FlaggedOption pAvaiable = new FlaggedOption(discover)
                .setStringParser(JSAP.INTEGER_PARSER)
                .setShortFlag('d')
                .setLongFlag(discover)
                .setRequired(false);
        c.setHelp("Visiting the avaiable points to discover holes.");
        c.registerParameter(pAvaiable);

        Switch pGroup = new Switch(group)
                .setShortFlag('g')
                .setLongFlag(group);
        c.setHelp("Grouping the closest points.");
        c.registerParameter(pGroup);

        c.setHelp("Scan the kanava to find holes.");

        put(c.getName(), c);
    }
}
